package org.forum.jstun;

import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class StunAttribute {
	public static final short TYPE_UNDEFINE				= 0x0000;
	public static final short TYPE_MAPPED_ADDRESS 		= 0x0001;
	public static final short TYPE_RESPONSE_ADDRESS 	= 0x0002;
	public static final short TYPE_CHANGE_REQUEST 		= 0x0003;
	public static final short TYPE_SOURCE_ADDRESS 		= 0x0004;
	public static final short TYPE_CHANGED_ADDRESS 		= 0x0005;
	public static final short TYPE_USERNAME 			= 0x0006;
	public static final short TYPE_PASSWORD 			= 0x0007;
	public static final short TYPE_MESSAGE_INTEGRITY 	= 0x0008;
	public static final short TYPE_ERROR_CODE 			= 0x0009;
	public static final short TYPE_UNKNOWN_ATTRIBUTES 	= 0x000A;
	public static final short TYPE_REFLECTED_FROM 		= 0x000B;

	// special attribute size
	public static final short HEADER_SIZE				= 4;
	public static final short MESSAGE_INTEGRITY_SIZE	= 4+20;
	public static final short MESSAGE_INTEGRITY_ALIGN	= 64;
	public static final short IPV4						= 0x01;
	public static final short IPV6						= 0x02;

	public static final short EC_BAD_REQUEST 			= 400;
	public static final short EC_UNAUTHORIZED 			= 401;
	public static final short EC_UNKNOWN_ATTRIBUTE 		= 420;

	public static final String EN_BAD_REQUEST 			= "Bad Request";
	public static final String EN_UNAUTHORIZED 			= "Unauthorized";
	public static final String EN_UNKNOWN_ATTRIBTE 		= "Unknown Attribute";

	protected short mType = TYPE_UNDEFINE;
	// length of value, not this attribute size
	protected short mLength = 0;
	protected byte[] mValue = null;

	public StunAttribute(){}
	public StunAttribute(short type) {
		mType = type;
	}

	public StunAttribute(short type, short length) {
		mType = type;
		mLength = length;
		if (length>0) {
			mValue = new byte[length];
			Arrays.fill(mValue, (byte)0);
		}
	}

	public StunAttribute(short type, byte[] value) {
		mType = type;
		mValue = value;
		mLength = (short)(value!=null ? value.length : 0);
	}

	public short getType() {
		return mType;
	}
	
	public short getLength() {
		return mLength;
	}

	public byte[] getBytes() {
		short length = getLength();
		byte[] data = new byte[2+2+length];
		System.arraycopy(hostToNetwork(getType()), 0, data, 0, 2);
		System.arraycopy(hostToNetwork(length), 0, data, 2, 2);
		if (mValue!=null && length>0)
			System.arraycopy(mValue, 0, data, 4, length);
		return data;
	}

	public void parseValue(byte[] data, int offset, int length) {
		if (data==null || data.length<offset+length) return;
		mValue = new byte[length];
		System.arraycopy(data, offset, mValue, 0, length);
	}

	public static byte[] hostToNetwork(short value) {
		// java host is little endian, network is big endian
		byte[] ret = new byte[2];
		ret[0] = (byte)((value>>8) & 0xFF);
		ret[1] = (byte)(value & 0xFF);
		return ret;
	}

	public static byte[] hostToNetwork(int value) {
		// java host is little endian, network is big endian
		byte[] ret = new byte[4];
		ret[0] = (byte)((value>>24) & 0xFF);
		ret[1] = (byte)((value>>16) & 0xFF);
		ret[2] = (byte)((value>>8) & 0xFF);
		ret[3] = (byte)(value & 0xFF);
		return ret;
	}

	public static int networkToHost(byte[] value, int offset, int length) {
		// network is big endian, java host is little endian
		if (value==null) return 0;
		if (value.length<offset+length) return 0;
		int ret = 0;
		for(int i=offset; i<offset+length; ++i)
			ret = (ret<<8) | (value[i]&0xFF);
		return ret;
	}

	public static boolean equalTo(byte[] dat0, int off0, byte[] dat1, int off1, int len) {
		if (dat0==null || dat1==null || dat0.length<off0+len || dat1.length<off1+len)
			return false;
		for(int i=0; i<len; ++i)
			if (dat0[off0+i]!=dat1[off1+i]) return false;
		return true;
	}

	/**
	 * @param text : to be digested input text byte[]
	 * @param key : secret key byte[]
	 * @param align : text align size, auto expand padding with 0
	 * @return
	 */
	public static byte[] getHmacSHA1(byte[] text, byte[] key, int align) {
		final String ALGORITHM = "HmacSHA1";
		byte[] ret = null;
		try {
			SecretKey skey = new SecretKeySpec(key, ALGORITHM);
			Mac mac = Mac.getInstance(ALGORITHM);
			mac.init(skey);
			mac.update(text);
			if ((text.length & (align-1))!=0) {
				byte[] padding = new byte[align-(text.length&(align-1))];
				Arrays.fill(padding, (byte)0);
				mac.update(padding);
			}
			ret = mac.doFinal();
		} catch (Exception e) {}
		return ret;
	}

}